package com.smart.service.impl;

import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.date.BetweenFormatter;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.thread.ExecutorBuilder;
import cn.hutool.core.util.NumberUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import com.smart.common.PageInfo;
import com.smart.common.WebResponse;
import com.smart.common.domain.BaseEntity;
import com.smart.common.enums.ErrorCode;
import com.smart.common.enums.OpenStatusEnum;
import com.smart.common.enums.OperateModuleEnum;
import com.smart.common.enums.OperateTypeEnum;
import com.smart.common.exception.BusinessException;
import com.smart.common.service.LogService;
import com.smart.common.service.RedisService;
import com.smart.common.utils.AssertUtil;
import com.smart.mapper.HotWorkMapper;
import com.smart.mapper.ProjectUseControlMapper;
import com.smart.mapper.StreetMapper;
import com.smart.model.customdo.UseControlCustomDO;
import com.smart.model.domain.ProjectUseControlDO;
import com.smart.model.domain.StreetDO;
import com.smart.model.dtomapper.ProjectUseControlDtoMapper;
import com.smart.model.request.OperateProjectRequest;
import com.smart.model.request.ProjectUseControlRequest;
import com.smart.model.util.ProjectUseControlConvert;
import com.smart.model.vo.ProjectUseControlVO;
import com.smart.service.ProjectUseControlService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.stream.Collectors;

/**
 * @author zhanghong
 */
@Service
public class ProjectUseControlServiceImpl extends ServiceImpl<ProjectUseControlMapper, ProjectUseControlDO> implements ProjectUseControlService {
    @Autowired
    private ProjectUseControlMapper projectUseControlMapper;
    @Autowired
    private LogService logService;
    @Autowired
    private HotWorkMapper hotWorkMapper;
    @Autowired
    private RedisService redisService;

    private ExecutorService executorService = ExecutorBuilder.create().setCorePoolSize(5).setMaxPoolSize(10).setWorkQueue(new LinkedBlockingDeque<>(20000)).build();

    @Override
    public ProjectUseControlVO getByProjectId(long projectId) {
        UseControlCustomDO projectDO = projectUseControlMapper.queryByProjectId2(projectId);
        AssertUtil.assertNotNull(projectDO, "项目数据不存在");
        return assembleProjectInfo(projectDO);
    }

    @Override
    public PageInfo<ProjectUseControlVO> pageQuery(OperateProjectRequest request) {
        if (request.getWillFunctionExpired()) {
            //计算最近一个月即将到期的开始和结束时间
            request.setWillExpiredStartTime(new Date());
            request.setWillExpiredEndTime(DateUtil.offsetMonth(new Date(), 1));
        }
        List<UseControlCustomDO> userControlList = projectUseControlMapper.listByCondition(request);

        // 条件过滤
        userControlList = filter(request, userControlList);
        int count = userControlList.size();
        List<UseControlCustomDO> page = ListUtil.page(request.getPageNum() - 1, request.getPageSize(), userControlList);

        List<CompletableFuture<ProjectUseControlVO>> futureList = Lists.newArrayList();
        for (UseControlCustomDO item : page) {
            CompletableFuture<ProjectUseControlVO> future = CompletableFuture.supplyAsync(() -> assembleProjectInfo(item), executorService);
            futureList.add(future);
        }
        List<ProjectUseControlVO> result = Lists.newArrayList();
        for (CompletableFuture<ProjectUseControlVO> future : futureList) {
            try {
                result.add(future.get());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return new PageInfo(request.getPageNum(), request.getPageSize(), result, count);
    }

    private List<UseControlCustomDO> filter(OperateProjectRequest request, List<UseControlCustomDO> result) {
        //最近一个月即将到期
        if (request.getWillFunctionExpired()) {
            result = result.stream().filter(item ->{
                long startTime = request.getWillExpiredStartTime().getTime();
                long endTime = request.getWillExpiredEndTime().getTime();
                if (item.getManageOpenExpire() != null) {
                    long manage = item.getManageOpenExpire().getTime();
                    if (manage >= startTime && manage <= endTime) {
                        return true;
                    }
                }

                if (item.getReportOpenExpire() != null) {
                    long report = item.getReportOpenExpire().getTime();
                    if (report >= startTime && report <= endTime) {
                        return true;
                    }
                }

                if (item.getHotWorkOpenExpire() != null) {
                    long hotWork = item.getHotWorkOpenExpire().getTime();
                    if (hotWork >= startTime && hotWork <= endTime) {
                        return true;
                    }
                }
                return false;
            }).collect(Collectors.toList());
        }
        //功能已过期
        if (request.getFunctionExpired()) {
            result = result.stream().filter(item ->{
                long now = (new Date()).getTime();
                if (item.getManageOpenExpire() != null) {
                    long manage = item.getManageOpenExpire().getTime();
                    if (now > manage) {
                        return true;
                    }
                }

                if (item.getReportOpenExpire() != null) {
                    long report = item.getReportOpenExpire().getTime();
                    if (now > report) {
                        return true;
                    }
                }

                if (item.getHotWorkOpenExpire() != null) {
                    long hotWork = item.getHotWorkOpenExpire().getTime();
                    if (now > hotWork) {
                        return true;
                    }
                }
                return false;
            }).collect(Collectors.toList());
        }
        //可作业次数已用完
        if (request.getHotWorkNumOut()) {
            result = result.stream().filter(item ->{
                if (item.getHotWorkOpenStatus() == null || Objects.equals(item.getHotWorkOpenStatus(), OpenStatusEnum.UN_OPEN.name()) || item.getHotWorkAvailableNum() == null) {
                    return false;
                }
                if (item.getHotWorkAvailableNum() == 0) {
                    return true;
                }
                return false;
            }).collect(Collectors.toList());
        }
        //可作业次数不足10%的数据
        if (request.getHotWorkTenPercent()) {
            result = result.stream().filter(item->{
                if (item.getHotWorkAvailableNum() != null && item.getHotWorkAvailableNum() != 0 && item.getHotWorkMax() != null) {
                    BigDecimal div = new BigDecimal("0");
                    if (item.getHotWorkMax() != 0) {
                        div = NumberUtil.div(item.getHotWorkAvailableNum(), item.getHotWorkMax(), 4);
                    }
                    if (div.doubleValue() < 0.1) {
                        return true;
                    }
                }
                return false;
            }).collect(Collectors.toList());
        }

        return result;
    }

    private ProjectUseControlVO assembleProjectInfo(UseControlCustomDO project) {
        if (project == null) {
            return null;
        }
        ProjectUseControlVO projectUseControlVO = new ProjectUseControlVO();
        projectUseControlVO.setProjectId(project.getProjectId());
        projectUseControlVO.setProjectName(project.getProjectName());
        projectUseControlVO.setProjectNumber(project.getProjectNumber());
        projectUseControlVO.setConstructionStatus(project.getConstructionStatus());
        projectUseControlVO.setStreetCode(project.getStreetCode());
        projectUseControlVO.setAddress(project.getAddress());
        projectUseControlVO.setEngineeringType(project.getEngineeringType());
        projectUseControlVO.setQualityRegistrationNumber(project.getQualityRegistrationNumber());

        // 查询开通控制
        ProjectUseControlDO controlDO = projectUseControlMapper.queryByProjectId(project.getProjectId());
        projectUseControlVO.setId(Optional.ofNullable(controlDO).map(BaseEntity::getId).orElse(null));
        projectUseControlVO.setManageOpenStatus(Optional.ofNullable(controlDO).map(ProjectUseControlDO::getManageOpenStatus).orElse(OpenStatusEnum.UN_OPEN.name()));
        projectUseControlVO.setManageOpenExpire(Optional.ofNullable(controlDO).map(ProjectUseControlDO::getManageOpenExpire).orElse(null));
        projectUseControlVO.setReportOpenStatus(Optional.ofNullable(controlDO).map(ProjectUseControlDO::getReportOpenStatus).orElse(OpenStatusEnum.UN_OPEN.name()));
        projectUseControlVO.setReportOpenExpire(Optional.ofNullable(controlDO).map(ProjectUseControlDO::getReportOpenExpire).orElse(null));
        projectUseControlVO.setHotWorkOpenStatus(Optional.ofNullable(controlDO).map(ProjectUseControlDO::getHotWorkOpenStatus).orElse(OpenStatusEnum.UN_OPEN.name()));
        projectUseControlVO.setHotWorkOpenExpire(Optional.ofNullable(controlDO).map(ProjectUseControlDO::getHotWorkOpenExpire).orElse(null));
        projectUseControlVO.setHotWorkMax(Optional.ofNullable(controlDO).map(ProjectUseControlDO::getHotWorkMax).orElse(null));
        projectUseControlVO.setHotWorkAvailableNum(Optional.ofNullable(controlDO).map(ProjectUseControlDO::getHotWorkAvailableNum).orElse(null));
        return projectUseControlVO;
    }

    @Override
    public List<ProjectUseControlVO> listQuery(ProjectUseControlRequest request) {
        List<ProjectUseControlDO> list = list();
        List<ProjectUseControlVO> projectUseControlVOS = ProjectUseControlConvert.buildProjectUseControlVoList(list);
        return projectUseControlVOS;
    }

    @Override
    public Boolean add(ProjectUseControlRequest request) {
        ProjectUseControlDO projectUseControlDO = ProjectUseControlDtoMapper.INSTANCE.request2DO(request);
        save(projectUseControlDO);
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean updateByKey(ProjectUseControlRequest request) {
        String beforeContent = null;
        if (request.getId() != null) {
            beforeContent = JSON.toJSONString(getById(request.getId()));
        }
        request.setHotWorkAvailableNum(request.getHotWorkMax());
        ProjectUseControlDO controlDO = projectUseControlMapper.queryByProjectId(request.getProjectId());
        if (request.getId() == null) {
            if (controlDO != null) {
                request.setId(controlDO.getId());
            }
        }
        if (!ObjectUtils.isEmpty(controlDO) && Objects.equals(request.getHotWorkOpenStatus(), OpenStatusEnum.OPENED.name())) {
            Long projectId = controlDO.getProjectId();
            //查询动火作业数
            Integer count = hotWorkMapper.selectCountByProjectId(projectId);
            if (count > 0) {
                AssertUtil.assertTrue(request.getHotWorkMax() >= count, "作业次数上限不能小于当前项目已提交的作业次数");
                request.setHotWorkAvailableNum(request.getHotWorkMax() - count);
            }
        }

        //模型数据转换
        ProjectUseControlDO projectUseControlDO = ProjectUseControlDtoMapper.INSTANCE.request2DO(request);
        if (projectUseControlDO.getManageOpenExpire() != null) {
            DateTime endOfDay = DateUtil.endOfDay(projectUseControlDO.getManageOpenExpire());
            projectUseControlDO.setManageOpenExpire(DateUtil.offsetSecond(endOfDay, -1));
        }
        if (projectUseControlDO.getReportOpenExpire() != null) {
            DateTime endOfDay = DateUtil.endOfDay(projectUseControlDO.getReportOpenExpire());
            projectUseControlDO.setReportOpenExpire(DateUtil.offsetSecond(endOfDay, -1));
        }
        if (projectUseControlDO.getHotWorkOpenExpire() != null) {
            DateTime endOfDay = DateUtil.endOfDay(projectUseControlDO.getHotWorkOpenExpire());
            projectUseControlDO.setHotWorkOpenExpire(DateUtil.offsetSecond(endOfDay, -1));
        }
        if (Objects.equals(projectUseControlDO.getHotWorkOpenStatus(), OpenStatusEnum.UN_OPEN.name())) {
            projectUseControlDO.setHotWorkAvailableNum(0L);
            projectUseControlDO.setHotWorkAvailableNum(0L);
        }
        saveOrUpdate(projectUseControlDO);

        //记录操作日志
        OperateTypeEnum operateType = request.getId() == null ? OperateTypeEnum.新建 : OperateTypeEnum.修改;
        String afterContent = JSON.toJSONString(getById(request.getId()));
        logService.logOperatePlatform(OperateModuleEnum.项目使用控制, operateType, request.getId(), beforeContent, afterContent);
        return true;
    }

    @Override
    public Boolean deleteByKey(long id) {
        projectUseControlMapper.deleteDOById(id);
        return true;
    }

    @Override
    public Boolean manageOpen(long projectId) {
        ProjectUseControlVO controlVO = getByProjectId(projectId);
        AssertUtil.assertTrue(Objects.equals(controlVO.getManageOpenStatus(), OpenStatusEnum.OPENED.name()), ErrorCode.SUCCESS, "办理人员进场需通过人脸身份识别一体机，请联系平台技术服务支持提供。");
        AssertUtil.assertNotNull(controlVO.getManageOpenExpire(), ErrorCode.SUCCESS, "人员管理功能已到期，请联系平台技术服务支持。");
        AssertUtil.assertTrue(controlVO.getManageOpenExpire().getTime() >= (new Date()).getTime(), ErrorCode.SUCCESS, "人员管理功能已到期，请联系平台技术服务支持。");
        return true;
    }

    @Override
    public Boolean reportOpenValid(long projectId) {
        ProjectUseControlVO controlVO = getByProjectId(projectId);
        AssertUtil.assertTrue(Objects.equals(controlVO.getReportOpenStatus(), OpenStatusEnum.OPENED.name()), ErrorCode.SUCCESS, "平台尚未给您开通提报功能，请联系平台技术服务支持。");
        AssertUtil.assertNotNull(controlVO.getReportOpenExpire(), ErrorCode.SUCCESS, "周报提报功能已到期，请联系平台技术服务支持。");
        AssertUtil.assertTrue(controlVO.getReportOpenExpire().getTime() >= (new Date()).getTime(), ErrorCode.SUCCESS, "周报提报功能已到期，请联系平台技术服务支持。");
        return true;
    }

    @Override
    public Boolean hotWorkOpenValid(long projectId) {
        ProjectUseControlVO controlVO = getByProjectId(projectId);
        AssertUtil.assertTrue(Objects.equals(controlVO.getHotWorkOpenStatus(), OpenStatusEnum.OPENED.name()), ErrorCode.SUCCESS, "平台尚未给您开通动火作业管理功能，请联系平台技术服务支持。");
        AssertUtil.assertNotNull(controlVO.getHotWorkOpenExpire(), ErrorCode.SUCCESS, "动火作业管理功能已到期，请联系平台技术服务支持。");
        AssertUtil.assertTrue(controlVO.getHotWorkOpenExpire().getTime() >= (new Date()).getTime(), ErrorCode.SUCCESS, "动火作业管理功能已到期，请联系平台技术服务支持。");
        AssertUtil.assertNotNull(controlVO.getHotWorkAvailableNum(), ErrorCode.SUCCESS, "动火作业提交次数已达上限，请联系平台技术服务支持。");
        AssertUtil.assertTrue(controlVO.getHotWorkAvailableNum() > 0, ErrorCode.SUCCESS, "动火作业提交次数已达上限，请联系平台技术服务支持。");
        return true;
    }

    @Override
    public Boolean hotWorkScheduleOpenValid(long projectId) {
        ProjectUseControlVO controlVO = getByProjectId(projectId);
        if (!Objects.equals(controlVO.getHotWorkOpenStatus(), OpenStatusEnum.OPENED.name())){
            return false;
        }
        if (controlVO.getHotWorkOpenExpire() == null){
            return false;
        }
        if (!(controlVO.getHotWorkOpenExpire().getTime() >= (new Date()).getTime())){
            return false;
        }

        return true;
    }

    @Override
    public Boolean hotWorkManagementScheduleOpenValid(long projectId) {
        ProjectUseControlVO controlVO = getByProjectId(projectId);
        if (!Objects.equals(controlVO.getHotWorkOpenStatus(), OpenStatusEnum.OPENED.name())){
            return false;
        }
        return true;
    }

    @Override
    public Boolean hotWorkOpenStatus(long projectId) {
        ProjectUseControlVO controlVO = getByProjectId(projectId);
        return Objects.equals(controlVO.getHotWorkOpenStatus(), OpenStatusEnum.OPENED.name());
    }

    @Override
    public Object tip(long projectId) {
        getByProjectId(projectId);
        Date date = new Date();
        DateTime dateTime = DateUtil.endOfWeek(date);
        long between = DateUtil.between(date, dateTime, DateUnit.MINUTE);
        int weekNum = DateUtil.weekOfYear(date);
        String key = String.format("smart:tip:%s:%s", projectId, weekNum);
        Map<String, String> value = redisService.getKey(key, Map.class);
        if (value == null) {
            value = new HashMap<>();
            // 30分钟
            Integer offsetSetSeconds = 30 * 60 -1;
            String tipEndTime = DateUtil.offsetSecond(date, offsetSetSeconds).toString(DatePattern.NORM_DATETIME_PATTERN);
            value.put("tipEndTime", tipEndTime);
            value.put("showPop", Boolean.TRUE.toString());
            redisService.set(key, JSON.toJSONString(value), between);
        } else {
            DateTime tipEndTime = DateUtil.parse(value.get("tipEndTime"), DatePattern.NORM_DATETIME_PATTERN);
            if (DateUtil.compare(tipEndTime, date) <= 0) {
                // 已过期
                value.put("showPop", Boolean.FALSE.toString());
                redisService.set(key, JSON.toJSONString(value), between);
            }
        }
        return value;
    }

    @Override
    public Boolean noHotWork(long projectId) {
        getByProjectId(projectId);
        Date date = new Date();
        int weekNum = DateUtil.weekOfYear(date);
        String key = String.format("smart:tip:%s:%s", projectId, weekNum);
        Map<String, String> value = redisService.getKey(key, Map.class);
        value.put("showPop", Boolean.FALSE.toString());
        DateTime dateTime = DateUtil.endOfWeek(date);
        long between = DateUtil.between(date, dateTime, DateUnit.MINUTE);
        redisService.set(key, JSON.toJSONString(value), between);
        return true;
    }
}
